06. Animations with MotionLayout

Kotlin SM A05 Exercise 1

Animations with MotionLayout

For this step you will build an animation that moves a view from the top start of the screen to the bottom end in response to user clicks.

After you complete this step, you'll have implemented the following animation.

The starter code for this step is missing several important elements you will add.

Because of this, MotionLayout will not display this screen correctly and you will see an empty screen or experience a crash until you complete this step.

To create the animation above with MotionLayout, you'll need the following major pieces

  • A MotionLayout which is a subclass of ConstraintLayout. You specify all the views to be animated in the MotionLayout tag.
  • A MotionScene which is an XML file that describes an animation for MotionLayout.
  • A Transition which is part of the MotionScene that specifies the animation duration, trigger, and how to move the views.
  • A ConstraintSet that specifies both the start and the end constraints of the transition.

Let's take a look at each of these in turn starting with the MotionLayout.

Introducing MotionLayout

MotionLayout is a subclass of ConstraintLayout, so it supports all the same features while adding animation. To use MotionLayout, you just use a MotionLayout view where you would use ConstraintLayout

In res/layout, open activity_step1.xml. Notice a MotionLayout with a single ImageView of a star with a tint applied inside of it.

<!-- initial code -->
<!-- activity_step1.xml -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

</androidx.constraintlayout.motion.widget.MotionLayout>

This MotionLayout will behave the same as a full screen ConstraintLayout, **but as of yet you haven't added any constraints. If you ran the app now, the screen would crash because you haven't added a **MotionScene.

Add a MotionScene

To animate using MotionLayout you need to specify a motion scene for the MotionLayout to use. This will let you describe the animation you want to build.

A motion scene is a single XML file that describes an animation in a MotionLayout.

For your layout to use a motion scene, it has to point at it.

  1. Update your layout to point to a motion scene. Add an app:layoutDescriptionattribute to the MotionLayout and point it to a MotionScene file which you will call @xml/step1.
<!-- activity_step1.xml →
<!-- add app:layoutDescription="@xml/step1" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       app:layoutDescription="@xml/step1">

Important: Ensure that you add app:layoutDescription before continuing.

Without the motion scene file, MotionLayout will not display this screen correctly and you will see an empty screen or experience a crash.

Declare a motion scene

  1. To define the motion scene, create an XML file at res/xml/step1.xml and replace the default contents with this MotionScene tag and comments:
<?xml version="1.0" encoding="utf-8"?>
<!-- xml/step1.xml -->

<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android"> 
    <!-- TODO: Define a Transition -->

    <!-- TODO: Define @id/start -->

    <!-- TODO: Define @id/end -->
</MotionScene>

Define Start and End constraints

All animations can be defined in terms of a start and an end. The start describes what the screen looks like before the animation, and the end describes what the screen looks like after the animation has completed. MotionLayout is responsible for figuring out how to animate between the start and end state (over time).

MotionScene uses a ConstraintSet tag to define the start and end states. A ConstraintSet is what it sounds like, a set of constraints that can be applied to views. This includes width, height, and ConstraintLayout constraints. It also includes some attributes such as alpha. It doesn't contain the views themselves, just the constraints on those views.

Any constraints specified in a ConstraintSet will override the constraints specified in the layout file. If you define constraints in both the layout and the MotionScene, only the constraints in the MotionScene are applied.

A ConstraintSet is a child of <MotionScene> that defines a set of constraints that will be applied to one or more views declared in your layout file. These constraints can be used to define the start or the end of an animation.

Constraint sets contain only contain the constraints or layout information such as width, height, alpha, or visibility. They don't contain other information such as the type of view or any view specific attributes like text on a TextView.

In this exercise, you will constrain the star view to the top start of the screen to start, and the bottom end of the screen at the end.

  1. Inside the MotionScene, replace the TODO for @id/start with the following ConstraintSet.
<!-- xml/step1.xml -->
<!-- TODO: Define @id/start -->

<!-- Constraints to apply at the beginning of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

The ConstraintSet has an ID of @id/start, and specifies all the constraints to apply to all the views in the MotionLayout. Since this MotionLayout only has one view, it only needs one Constraint.

The Constraint inside the ConstraintSet specifies the id of the view that it's constraining, @id/red_star defined in activity_step1.xml. It's important to note that Constraint tags specify only constraints and layout information - the Constraint tag doesn't know that it's being applied to an ImageView.

This constraint specifies the height, width, and the two other constraints needed to constrain the red_star view to the top start of its parent.

  1. Next, specify the ConstraintSet for the end of the star animation in the MotionScene:
<!-- xml/step1.xml →
<!-- TODO: Define @id/end -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

Just like @id/start, this ConstraintSet has a single Constraint on @id/red_star. This time it constrains it to the bottom end of the screen.

You don't have to name them @id/start and @id/end, but it is convenient to do so.

When using MotionLayout, you should specify the constraints of any views that are animated in the motion scene XML file instead of the layout. Any views that are not animated may be constrained in the layout instead of the motion scene.

Define a Transition

Every MotionScene must also include at least one transition. A transition defines every part of one animation, from start to end.

A transition must specify a start and end ConstraintSet for the transition. In addition, a transition can specify how to modify the animation in other ways, such as how long to run the animation or how to animate by dragging views.

  1. Inside the MotionScene, replace the TODO to define a transition:
<!-- xml/step1.xml -->
<!-- TODO: Define a Transition -->

<!-- A transition describes an animation via start and end state -->
<Transition
    app:constraintSetStart="@+id/start"
    app:constraintSetEnd="@+id/end"
    app:duration="2000">
    <!-- TODO: Handle clicks -->
</Transition>

This is everything that MotionLayout needs to build an animation. Looking at each attribute:

  • constraintSetStart will be applied to the views as the animation starts
  • constraintSetEnd will be applied to the views at the end of the animation
  • duration specifies how long the animation should take (ms)

MotionLayout will then figure out a path between the start and end constraints and animate it for the specified duration.

We need a way to start the animation. One way to do this is to make the MotionLayout respond to click events on @id/red_star.

  1. To make MotionLayout respond to click events update the transition to have an OnClick tag.
<!-- xml/step1.xml -->
<!-- TODO: Handle clicks -->

<!-- A transition describes an animation via start and end state -->
<Transition
    app:constraintSetStart="@+id/start"
    app:constraintSetEnd="@+id/end"
    app:duration="2000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        app:targetId="@id/red_star"
        app:clickAction="toggle" />
</Transition>

The Transition tells MotionLayout to run the animation in response to click events using an <OnClick> tag. Looking at each attribute:

  • targetId' is the view to watch for clicks
  • 'clickAction' of toggle will switch between the start and end state on click, you can see other options for clickAction in the documentation
  1. Run your code, click Step 1, then click the red star and see the animation!

A Transition describes one animation in a MotionScene.
It contains the specification for a complete animation to MotionLayout, including all views that change during the animation, how long the animation should take, and how to handle user touch to drive the animation.

A MotionScene can have more than one animation and more than one Transition.

Note: It is possible to load a ConstraintSet from an existing ConstraintLayout by pointing start or end to a layout file instead of a ConstraintSet. However, we don't recommend this as it's harder to keep the code in sync between multiple files.

Animations in action

If you run the app, you see your animation running when you click on the star.

The completed motion scene file defines one Transition which points to a start and end ConstraintSet.

At the start of the animation (@id/start), the star icon is constrained to the top start of the screen. At the end of the animation (@id/end) the star icon is constrained to the bottom end of the screen.

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           app:constraintSetStart="@+id/start"
           app:constraintSetEnd="@+id/end"
           app:duration="2000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               app:targetId="@id/red_star"
               app:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               app:layout_constraintStart_toStartOf="parent"
               app:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               app:layout_constraintEnd_toEndOf="parent"
               app:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>